#include <sys/types.h>
#include <sys/sem.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>		/* for variable arg lists */
#include <stdlib.h>

#include "my_semaphore.h"

#define	MAX_TRIES	10	/* for waiting for initialization */

#ifdef	__bsdi__
#define	va_mode_t	int
#else
#define	va_mode_t	mode_t
#endif

#ifndef	HAVE_SEMUN_UNION
union semun {				/* define union for semctl() */
  int              val;
  struct semid_ds *buf;
  unsigned short  *array;
};
#endif	/* HAVE_SYS_SEM_H */

sem_t *
qsem_open(const char *pathname, int oflag, ... )
{
	int     i, fd, semflag, semid, save_errno;
	key_t	key;
	mode_t	mode;
	va_list	ap;
	sem_t	*sem;
	union semun arg;
	unsigned int	value;
	struct semid_ds	seminfo;
	struct sembuf	initop;

	/* 4no mode for sem_open() w/out O_CREAT; guess */
	semflag = SVSEM_MODE;
	semid   = -1;
	if (oflag & O_CREAT) {
		va_start(ap, oflag);		/* init ap to final named argument */
		mode  = va_arg(ap, va_mode_t);
		value = va_arg(ap, unsigned int);
		va_end(ap);

		/* 4convert to key that will identify System V semaphore */
		if ((fd = open(pathname, oflag, mode)) == -1)
			return(SEM_FAILED);
		close(fd);
		if ((key = ftok(pathname, 0)) == (key_t) -1)
			return(SEM_FAILED);

		semflag = IPC_CREAT | (mode & 0777);
		if (oflag & O_EXCL)
			semflag |= IPC_EXCL;

			/* 4create the System V semaphore with IPC_EXCL */
		if ((semid = semget(key, 1, semflag | IPC_EXCL)) >= 0) {
				/* 4success, we're the first so initialize to 0 */
			arg.val = 0;
			if (semctl(semid, 0, SETVAL, arg) == -1)
				goto err;
				/* 4then increment by value to set sem_otime nonzero */
			if (value > SEMVMX) {
				errno = EINVAL;
				goto err;
			}
			initop.sem_num = 0;
			initop.sem_op  = value;
			initop.sem_flg = 0;
			if (semop(semid, &initop, 1) == -1)
				goto err;
			goto finish;

		} else if (errno != EEXIST || (semflag & IPC_EXCL) != 0)
			goto err;
		/* else fall through */
	}
	
	/*
	 * (O_CREAT not secified) or
	 * (O_CREAT without O_EXCL and semaphore already exists).
	 * Must open semaphore and make certain it has been initialized.
	 */
	if ((key = ftok(pathname, 0)) == (key_t) -1)
		goto err;
	
	if ((semid = semget(key, 0, semflag)) == -1)
		goto err;

	arg.buf = &seminfo;
	for(i = 0; i < MAX_TRIES; i++) {
		if (semctl(semid, 0, IPC_STAT, arg) == -1)
			goto err;
		if (arg.buf->sem_otime != 0)
			goto finish;
		sleep(1);
	}
	errno = ETIMEDOUT;
err:
	save_errno = errno;		/* don't let semctl() change errno */
	if (semid != -1)
		semctl(semid, 0, IPC_RMID);
	errno = save_errno;
	return(SEM_FAILED);

finish:
/* *INDENT-OFF* */
	if ((sem = malloc(sizeof(sem_t))) == NULL)
		goto err;
/* *INDENT-ON* */
	sem->sem_semid = semid;
	sem->sem_magic = SEM_MAGIC;
	return(sem);
}

int
qsem_close(sem_t *sem)
{
	if (sem->sem_magic != SEM_MAGIC) {
		errno = EINVAL;
		return(-1);
	}
	sem->sem_magic = 0;		/* just in case */

	free(sem);
	return(0);
}

int
qsem_unlink(const char *pathname)
{
	int	semid;
	key_t	key;

	if ((key = ftok(pathname, 0)) == (key_t) -1)
		return(-1);
	if (unlink(pathname) == -1)
		return(-1);
	if ((semid = semget(key, 1, SVSEM_MODE)) == -1)
		return(-1);
	if (semctl(semid, 0, IPC_RMID) == -1)
		return(-1);
	return(0);
}

int
qsem_post(sem_t *sem)
{
	struct sembuf op;
	if (sem->sem_magic != SEM_MAGIC) {
		errno = EINVAL;
		return(-1);
	}

	op.sem_num = 0;
	op.sem_op = 1;
	op.sem_flg = 0;
	if (semop(sem->sem_semid, &op, 1) < 0)
		return(-1);
	return(0);
}

int
qsem_getvalue(sem_t *sem, int *pvalue)
{
	int val;
	if (sem->sem_magic != SEM_MAGIC) {
		errno = EINVAL;
		return(-1);
	}

	if ( (val = semctl(sem->sem_semid, 0, GETVAL)) < 0)
		return(-1);
	*pvalue = val;
	return(0);
}

int
qsem_wait(sem_t *sem)
{
	struct sembuf op;

	if (sem->sem_magic != SEM_MAGIC) {
		errno = EINVAL;
		return(-1);
	}

	op.sem_num = 0;
	op.sem_op = -1;
	op.sem_flg = 0;
	if (semop(sem->sem_semid, &op, 1) < 0)
		return(-1);
	return(0);
}

int
qsem_trywait(sem_t *sem)
{
	struct sembuf op;

	if (sem->sem_magic != SEM_MAGIC) {
		errno = EINVAL;
		return(-1);
	}

	op.sem_num = 0;
	op.sem_op = -1;
	op.sem_flg = IPC_NOWAIT;
	if (semop(sem->sem_semid, &op, 1) < 0)
		return(-1);
	return(0);
}
